package org.apache.lucene.index;

import java.io.IOException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import org.apache.lucene.store.IndexInput;
import org.apache.lucene.util.BitVector;

public abstract class TagBaseBlocksTermDocs implements TermDocs {
  private TagBlockTermDocs current;
  private TagBlockTermDocs[] termDocsArray;
  private TagBlockInfos blockInfos;
  int pointer;
  int base;
  private BitVector deletedDocs;
  int skipInterval;
  int maxSkipLevels;
  Term term;
  IndexInput[] streams;
  TagIndexSnapshot snapshot;
  private int[] starts;

  public TagBaseBlocksTermDocs(TagIndexSnapshot snapshot, BitVector deletedDocs, int skipInterval, int maxSkipLevels) {
    this.snapshot = snapshot;
    starts = snapshot.tagIndex.starts;
    this.deletedDocs = deletedDocs;
    this.skipInterval = skipInterval;
    this.maxSkipLevels = maxSkipLevels;
  }
  
  public void close() throws IOException {
    Set set = new HashSet();
    for (int x=0; x < streams.length; x++) {
      set.add(streams[x]);
    }
    Iterator iterator = set.iterator();
    while (iterator.hasNext()) {
      IndexInput input = (IndexInput)iterator.next();
      input.close();
    }
  }
  
  public void seek(TagBlockInfos blockInfos, IndexInput stream, Term term) throws IOException {
    IndexInput[] streams = new IndexInput[blockInfos.positions.length];
    for (int x=0; x < streams.length; x++) {
      streams[x] = stream;
    }
    seek(blockInfos, streams, term);
  }
  
  public void seek(TagBlockInfos blockInfos, IndexInput[] streams, Term term) throws IOException {
    this.blockInfos = blockInfos;
    this.streams = streams;
    this.term = term;
  }

  public int doc() {
    return base + current.doc();
  }

  public boolean next() throws IOException {
    for (;;) {
      if (current != null && current.next()) {
        return true;
      } else if (pointer < termDocsArray.length) {
        base = starts[pointer];
        current = termDocs(pointer++);
      } else {
        return false;
      }
    }
  }

  private TagBlockTermDocs termDocs(int i) throws IOException {
    if (termDocsArray[i] == null) {
      termDocsArray[i] = new TagBlockTermDocs(i, blockInfos, deletedDocs, streams[i], skipInterval, maxSkipLevels);
    }
    return termDocsArray[i];
  }

  public int read(final int[] docs, final int[] freqs) throws IOException {
    while (true) {
      while (current == null) {
        if (pointer < blockInfos.positions.length) {
          base = starts[pointer];
          current = termDocs(pointer++);
        } else {
          return 0;
        }
      }
      int end = current.read(docs, freqs);
      if (end == 0) {
        current = null;
      } else {
        final int b = base;
        for (int i = 0; i < end; i++)
          docs[i] += b;
        return end;
      }
    }
  }

  public boolean skipTo(int target) throws IOException {
    for (;;) {
      if (current != null && current.skipTo(target - base)) {
        return true;
      } else if (pointer < blockInfos.positions.length) {
        // pointer = blockIndex(target, starts, blockInfos.length, pointer);
        pointer = binarySearch(starts, target, pointer, blockInfos.positions.length - pointer);
        base = starts[pointer];
        current = termDocs(pointer);
      } else
        return false;
    }
  }

  // from instantiatedindex
  public static final int binarySearch(int[] array, int key, int offset, int length) {
    if (length == 0) {
      return -1 - offset;
    }
    int min = offset, max = offset + length - 1;
    int minVal = array[min], maxVal = array[max];

    int nPreviousSteps = 0;

    // Uncomment these two lines to get statistics about the average number of
    // steps in the test report :
    // totalCalls++;
    for (;;) {
      // totalSteps++;

      // be careful not to compute key - minVal, for there might be an integer
      // overflow.
      if (key <= minVal)
        return key == minVal ? min : -1 - min;
      if (key >= maxVal)
        return key == maxVal ? max : -2 - max;

      assert min != max;

      int pivot;
      // A typical binarySearch algorithm uses pivot = (min + max) / 2.
      // The pivot we use here tries to be smarter and to choose a pivot close
      // to the expectable location of the key.
      // This reduces dramatically the number of steps needed to get to the key.
      // However, it does not work well with a logaritmic distribution of
      // values, for instance.
      // When the key is not found quickly the smart way, we switch to the
      // standard pivot.
      if (nPreviousSteps > 2) {
        pivot = (min + max) >> 1;
        // stop increasing nPreviousSteps from now on
      } else {
        // NOTE: We cannot do the following operations in int precision, because
        // there might be overflows.
        // long operations are slower than float operations with the hardware
        // this was tested on (intel core duo 2, JVM 1.6.0).
        // Overall, using float proved to be the safest and fastest approach.
        pivot = min + (int) ((key - (float) minVal) / (maxVal - (float) minVal) * (max - min));
        nPreviousSteps++;
      }

      int pivotVal = array[pivot];

      // NOTE: do not store key - pivotVal because of overflows
      if (key > pivotVal) {
        min = pivot + 1;
        max--;
      } else if (key == pivotVal) {
        return pivot;
      } else {
        min++;
        max = pivot - 1;
      }
      maxVal = array[max];
      minVal = array[min];
    }
  }

  public int freq() {
    return 1;
  }
}
